<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script type="module">
      // 触发更新视图
      const updateView = function () {
        console.log(`数据更新`)
      }

      // 重新定义数组的原型
      const oldArrayProperty = Array.prototype
      // 创建新对象，原型指向oldArrayProperty，再扩展新的方法不会影响原型
      const arrPrototype = Object.create(oldArrayProperty)
      ;['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(
        (methodName) => {
          arrPrototype[methodName] = function () {
            console.log('触发数组监听')
            updateView() // 触发视图更新
            oldArrayProperty[methodName].call(this, ...arguments)
          }
        }
      )

      // 重新定义属性，并监听
      const defineReactive = (target, key, value) => {
        // 深度监听
        observer(value)
        // 核心API
        Object.defineProperty(target, key, {
          get: function () {
            return value
          },
          set: function (newValue) {
            if (newValue !== value) {
              // 深度监听
              observer(newValue)
              // value一直处在闭包当中，此时设置完再get也是最新值
              value = newValue
              updateView()
            }
          }
        })
      }
      window.defineReactive = defineReactive

      // 监听对象属性
      const observer = (target) => {
        // 只监听对象或数组
        if (typeof target !== 'object' || target === null) {
          return target
        }

        // 额外处理数组的监听
        if (Array.isArray(target)) {
          target.__proto__ = arrPrototype
        }

        // 重新定义各个属性
        for (let key in target) {
          defineReactive(target, key, target[key])
        }
      }
      window.observer = observer

      const data = {
        name: '张三',
        age: 20,
        info: {
          address: '福州' // 需要深度监听
        },
        nums: [10, 20, 30]
      }

      observer(data)

      data.name = '李四'
      data.age = 21
      data.x = '100'
      delete data.name
      data.info.address = '福建'
      data.nums.push(40)
    </script>
  </head>
  <body></body>
</html>
